[EXPERIMENT] New models - #216
Conversation
Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
haikoschol
left a comment
There was a problem hiding this comment.
Apart from my confusion regarding vuln_id vs reference_ids, I think this proposal is an improvement over the current models and we should go with it.
I have one more thought regarding JSON vs array fields. I don't feel strongly about which one we end up using. I expect JSON fields to just be slightly more of a hassle to work with. Having said that, the reason for using JSON instead of array fields is that in a future version of Django there will be an abstraction for JSON columns that works with all supported databases, right? Where did this information come from? I couldn't find any information regarding these plans from a superficial search. I'm asking because I wonder whether there are plans to do the same for array fields.
|
|
||
|
|
||
| class ImpactedPackage(models.Model): | ||
| class Vulnerability_Package_Relation(models.Model): |
There was a problem hiding this comment.
I assume this name is a placeholder, right? How about VulnerabilityImpact? Not great, not terrible, IMHO.
There was a problem hiding this comment.
VulnerabilityImpact doesn't mention package anywhere, we need a name which should make sense that the table is about vulnerability and package
There was a problem hiding this comment.
Yeah that would be better.
There was a problem hiding this comment.
@sbs2001 I am not convinced by the name too (and we should not use snake case for Class or model names).
The attributes are about a Package (is_vulnerable and version_range are all about Package) so a better name could might PackageAssignedVulnerability or PackageRelatedVulnerability ... but I need to think more about that change as what are the benefits to combine the Impacted and Resolved models in one?
There was a problem hiding this comment.
About the name thing, it's just a placeholder, Btw PackageRelatedVulnerability makes tad more sense here.
what are the benefits to combine the Impacted and Resolved models in one?
Good question. There are some issues with having 2 tables, Impacted and Resolved .
Issue 1 :
You can do this, which doesn't make any sense.
In [1]: from vulnerabilities import models
In [2]: v1 = models.Vulnerability.objects.create(cve_id="CVE-foo")
In [3]: p1 = models.Package.objects.create(name="cream",type="ice",version='mango')
In [4]: vp1 = models.ImpactedPackage.objects.create(vulnerability=v1, package=p1)
In [5]: vp2 = models.ResolvedPackage.objects.create(vulnerability=v1, package=p1)
This is pure garbage, nothing can be interpreted from these entries.
With a single table + flag, I can use a unique_together=('vulnerability','package')
Issue 2 : Check https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/import_runner.py#L121 , I am not sure I understand the exact issue but it's something along the lines that updating vulnerability status of a already existing package is not possible. @haikoschol can you explain this, with a snippet?
Having a single table, changes delete to an update(of the flag), which bypasses this issue.
There was a problem hiding this comment.
Never mind @haikoschol already left a brief explanation of this issue in the comments at https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/tests/test_import_runner.py#L201
| cve_id = models.CharField(max_length=50, help_text='CVE ID', unique=True, null=True) | ||
| summary = models.TextField(help_text='Summary of the vulnerability', blank=True) | ||
| cvss = models.FloatField(max_length=100, help_text='CVSS Score', null=True) | ||
| vuln_id = models.CharField(max_length=50, help_text='eg CVE ID, RUST SEC ID', unique=True, null=True) |
There was a problem hiding this comment.
Should this field really be nullable? What do we do with a vulnerability that has no vuln_id and no reference_ids? If your response is that there is always at least one reference ID, then why not store that in vuln_id? Or in other words; I haven't quite understood the difference between vuln_id and reference_ids here.
There was a problem hiding this comment.
I haven't quite understood the difference between vuln_id and reference_ids here.
I expected that, hence I had added comments in the code to define what is vuln_id and what is reference_id, here is a repaste:
Whatever goes into vuln_id is a vulnerability identifier
which is undivisible i.e atomic vulnerability id. All CVEs fit into this.
reference_ids are usually but not limited toadvisoryids like USN-4399-1
Contents of reference_ids are a name/id given to collection of
other small vulnerbilties. For example USN-4399-1 refers to CVE-2020-8618, CVE-2020-8619
If your response is that there is always at least one reference ID
As far as the advisories I've looked at, yes there is some sort of id present, but I'm not 100% confident whether this will stay true.
then why not store that in vuln_id?
Yes. If it is a atomic vulnerability id, then it probably didn't belonged in the reference_id in the first place. RUST-SEC ids are stored in reference_id no matter whether they have CVE or not. If they don't have a CVE, they become atomic, because no other id will denote that specific vulnerability.
I also had this idea, which I didn't mentioned here, but a vuln_id's value should also be present along with(if present) other reference_ids in the reference_ids column. The idea being vuln_id is also it's own reference_id.
Should this field really be nullable? What do we do with a vulnerability that has no vuln_id and no reference_ids?
As @pombredanne mentioned, we have to give them our ids, but that's gonna introduce a whole lot of other complexities(how to make id's consistent ?).
My other point is , should we really worry about vulnerabilities without any id's . As far as I have inspected these advisories, only FriendsOfPHP were missing these , which was solved, because GH provide their ids for FriendsOfPHP advisories.
There was a problem hiding this comment.
Sorry, I should have mentioned that I read the comment and still didn't get it (undivisible/atomic and "small vulnerabilities" confused me). But now I think I understand. Some advisories cover multiple vulnerabilities and if those have CVE IDs, they will all be mentioned.
The problem with storing IDs other than CVE in vuln_id is that we require them to be unique across all publishers of advisories. That might be the case coincidentally, but I don't think there are any efforts to ensure that. But, in practice it will probably work and if not that problem can be solved when it occurs.
As @pombredanne mentioned, we have to give them our ids, but that's gonna introduce a whole lot of other complexities(how to make id's consistent ?).
I think that was just referring to the automatically added primary key column.
I don't think we should worry about vulnerabilities without IDs. But the only reason I can think of for making this column nullable is to be able to store vulnerabilities without IDs. Hence my question. :)
There was a problem hiding this comment.
@sbs2001 re:
As far as I have inspected these advisories, only FriendsOfPHP were missing these
AFAIK, they use date as ID then FriendsOfPHP/security-advisories@c6fc722#diff-a1ec953bbcb767e15ba1a9edbe828550
There was a problem hiding this comment.
@sbs2001 I thin we could do better with a simpler model.
- On the
vuln_id, if we want and need this, it would then becomes our own id that we assign automatically IMHO. I am not sure we need an id though I can some benefit for users. - On the reference side, IMHO one URL + reference ID is a reference, I cannot see when we need more than one URL. Can you elaborate that?
There was a problem hiding this comment.
@pombredanne re
On the reference side, IMHO one URL + reference ID is a reference, I cannot see when we need more than one URL. Can you elaborate that?
I have explained it in this ticket itself, can you take a look at Problem 1 ?
There was a problem hiding this comment.
AFAIK, they use date as ID then
Probably yes.
On the reference side, IMHO one URL + reference ID is a reference, I cannot see when we need more than one URL. Can you elaborate that?
Sure, I have done that in a comment below
There was a problem hiding this comment.
On the vuln_id, if we want and need this, it would then becomes our own id that we assign automatically IMHO. I am not sure we need an id though I can some benefit for users.
I don't understand this, can you elaborate this further?
|
@haikoschol re
https://docs.djangoproject.com/en/3.1/releases/3.1/#jsonfield-for-all-supported-database-backends , django/django#12392 , it was a GSoC 2019 project :) |
|
@sbs2001 re:
Rather I think we should get in this example only two rows as:
|
|
@pombredanne re
Exactly, that's the expected thing. FWIW there are cases where there are reference ids without corresponding urls and vice versa but that's not the issue. The problem is, as the current So the next best thing I could think of is throw all the urls and ref_ids in a JSONField. |
|
@pombredanne @haikoschol btw please mention things which you think are correctly implemented, once I have your aproval I can get started on that thing ASAP. |
|
re: https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/import_runner.py#L111 for id_ in advisory.reference_ids:
models.VulnerabilityReference.objects.get_or_create(
vulnerability=vuln, reference_id=id_)
for url in advisory.reference_urls:
models.VulnerabilityReference.objects.get_or_create(vulnerability=vuln, url=url)The issue here is NOT the DB model but the Advisory data structure in https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/data_source.py#L60 This reference_urls: Sequence[str] = dataclasses.field(default_factory=list)
reference_ids: Sequence[str] = dataclasses.field(default_factory=list)should be instead its own data structure that strictly mirrors the DB model, e.g. more or less something like that: @dataclasses.dataclass
class VulnerabilityReference: # use same name as model for now
source = str
reference_id = str
url = str
[.....]
@dataclasses.dataclass
class Advisory
references: Sequence[VulnerabilityReference] = dataclasses.field(default_factory=list)Then the code in Importers at https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/import_runner.py#L111 would be more or less something like this: # build indexes of existing references to either update or create new ones
# and avoid creating duplicates
refs_by_id_url = {(ref.id, ref.url): ref for ref in vuln.references if ref.id and ref.url}
refs_by_id = {ref.id: ref for ref in vuln.references if ref.id}
refs_by_url = {ref.url: ref for ref in vuln.references if ref.url}
for adv_ref in advisory.references:
if adv_ref.reference_id and adv_ref.url:
existing_ref = refs_by_id_url.get((adv_ref.id, adv_ref.url))
elif adv_ref.reference_id:
existing_ref = refs_by_id.get(adv_ref.id)
elif adv_ref.reference_url:
existing_ref = refs_by_id.get(adv_ref.url)
if existing_ref:
# update ref as neeed
if adv_ref.summary and not existing_ref.summary:
existing_ref.summary = adv_ref.summary
if adv_ref.reference_id and not existing_ref.reference_id:
existing_ref.reference_id = adv_ref.reference_id
if adv_ref.url and not existing_ref.url:
existing_ref.url = adv_ref.url
if adv_ref.source and not existing_ref.source:
existing_ref.source = adv_ref.source
existing_ref.save()
else:
new_ref = VulnerabilityReference(
vulnerability=vuln,
reference_id=ref.reference_id,
url=adv_ref.url,
summary=adv_ref.summary,
source=adv_ref.source,
)
new_ref.save() |
Yes they can be used, with one caveat , as they don't a pk(yet) they are unhashable, hence can't be put in sets. I have experienced problems due to this in #219 , where bulk inserting/updating was done. But simple workaround for this exists.
It is, @haikoschol and I had chat about this awhile ago on our gitter channel. The code was written assuming there is no relation between url and the id , which is true in some cases, while false in others, hence the assumption. This can be actually solved by tweaking the advisory model albeit. ............
class Advisory :
............
vulnerability_references = Sequence[Mapping[str,str]]eg value of vulnerability_references = [{'url':'usn123.com','reference_id':'usn-123'}] In the importer for vulnerability_reference in advisory.vulnerability_references :
VulnerabilityReference.objects.get_or_create(**vulnerability_reference) |
|
IMHO the get_or_create is too simplistic and in most case an actual object with named attributes is cleaner than a mapping, hence why I do not like too much |
|
Also with:
... you are missing out that thing should be merged and updated rather than just created or got IMHO |
IMHO there is always a relation between the two. |
|
@pombredanne re
ack :) I messed up , but that's minor we can handle that, by doing something along the lines of the snippet you posted above.
That's a valid point in this case, how about class VulnerabilityReferenceArgs :
url : str = ''
reference_id : str =''
def __post_init__(self) :
if not any([self.url, self.reference_id]):
raise SomeErrorAnd have |
👍 |
|
Repaste from https://gitter.im/aboutcode-org/vulnerablecode
Which meant we would have multiple VulnerabilityReference for a given pair of Vulnerability and Source. Now the question was of how to relate a VulnerabilityScore to a Vulnerability. Previously we thought this would best fit, as shown in the top comment, i.e with a FK of vulnerabilityreference because the assumption was we would have 1 VulnerabilityRef per pair of Source and Vulnerability. The assumption is not true anymore.
|
|
VulnerabilityScore should be related to a VulnerabilityReference and not to a Vulnerability |
|
@sbs2001 in reply to #216 (comment)
This is only a theoretical problem. Just do not do it :)
Note sure I get the details, but I think we have abused using |
I thought it would be ok since the old models had score in Vulnerability NOT in VulnerabilityReference. What we are trying to acheive here is to store multiple scores for a single Vulnerability. Using score related VulnerabilityReference makes it very tricky to figure out of which VulnerabilityReference , is the VulnerabilityScore based of. |
IMHO rather we are trying to attach the score to a given reference which is really the only thing we can be sure of. And a ref (with its score) may be linked in multiple Vulnerabilities |
Why so? I see it this way, for a given vulnerability
|
|
@pombredanne re
I disagree, there are instances where the sources contradict themselves, in such cases IMHO we should bring it to their notice, community curation :) . I have posted in chat about how archlinux advisory has 2 contradicting entries |
I have a FK of 'source' on score already, so it would mean the same. |

Signed-off-by: Shivam Sandbhor shivam.sandbhor@gmail.com
The problems and how the new models solve them:
Problem 1 : A row in VulnerabilityReference does not properly encapsulate the reference ids and urls. Consider an importer encounters 2 distinct reference ids for a vulnerability, say USN-A and DSA-B and 2 corresponding urls 'www.USN-A.org' and 'www.DSA-B.org'. With the current implementation, A VulnerabilityReference can contain only single url and a single reference_id. What ends up happening is we either end up making 4 VulnerabilityReference row, like
|....|USN A | NULL |
|....|DSA B | NULL |
|....|NULL | 'www.USN-A.org' |
|....|NULL | 'www.DSA-B.org'|
or have rows of combinations of reference_id and url , which might be unrelated.
Problem 2: The
summaryof a vulnerability does not really belong in the Vulnerability table. Remember that vendors publish advisories in context of vulnerable packages, so the summary is usually related to the affected package + the vulnerability's nature. What happens in current implementation is, that thesummaryis overwritted and replaced by thesummaryfound by last importer.Problem 3: Vulnerability scores, check #157
What this model lacks :